import { TAllTypes, 
    IPlusMinus, 
    IValueString, 
    IBrackets, 
    IOperate, 
    IValueNumber, 
    OperateSymbol, 
    IPosition, 
    BASE_OPERATE, 
    IPositionFullRow,
    IKeywords,
    IFunction,
    IPositionFullCol
} from './types';
import { TableHelper } from '../table-helper';
import { FormulaError } from './formula-error';
import { ISpecial } from '../formula.v2';

type ErrorInfo = null|FormulaError;
type ParseFunctionResult = [ErrorInfo, TAllTypes, number]
type ParseFunction = (str: string, startIdx: number, pastParse: TAllTypes[]) => ParseFunctionResult;
let KEYWORDS = {
    'TRUE': true,
    'FALSE': false
}
type SplitSymbol = '"'|"'";
const parseString: ParseFunction = (str: string, start: number) => {
    const stack: ('"'|"'")[] = [];
    stack.push(str[start] as SplitSymbol);
    for(let i = start+1; i < str.length; i++) {
        if(str[i] === '"' || str[i] === "'") {
            if(stack[stack.length - 1] === str[i]) {
                stack.pop();
                if(stack.length === 0) {
                    let val: IValueString = {
                        type: 'value',
                        dataType: 'string',
                        value: str.substring(start + 1, i)
                    };
                    return [null, val, i];
                }
            } else {
                stack.push(str[i] as SplitSymbol);
            }
        }  
    }
    return [new FormulaError('#NAME?'), {type: 'error', msg: 'parseString'}, str.length -1];
}
const paseOperate:ParseFunction = (str: string, start: number, result: TAllTypes[]) => {
    let pastParse = result[result.length -1] as IBrackets | IOperate;
    if((start === 0) || ((str[start] === '+'||str[start] === '-') && result.length > 0 && ((pastParse.type === 'brackets' && pastParse.value === 'left')) || pastParse.type === 'operate')) {
        let val:IPlusMinus = {
            type: 'plusMinus',
            value: str[start] === '+' ? 'plus':'minus'
        }
        return [null, val, start];
    }
    if(start < str.length - 1 && ['>', '<', '*'].indexOf(str[start]) !== -1 && (BASE_OPERATE.indexOf(str[start + 1] as OperateSymbol) !== -1 || str[start + 1] === '=') && ['+', '-'].indexOf(str[start+1]) === -1) {
        let val: IOperate = {
            type: 'operate',
            value: str.substring(start, start+2) as OperateSymbol
        }
        return [null, val, start+1];
    }
    if(str[start] === '!' || str[start] === '=') {
        if(str[start + 1] !== '=') {
            // throw error;
        }
        let val: IOperate = {
            type: 'operate',
            value: str.substring(start, start+1) as OperateSymbol
        }
        return [null, val, start+1];
    }
    
    let val: IOperate = {
        type: 'operate',
        value: str[start] as OperateSymbol
    }
    return [null, val, start];
}
const parseNumber: ParseFunction = (str: string, start: number) => {
    let endIdx = str.length - 1;
    for(let i = start; i < str.length; i++) {
        if(!(/[0-9]/.test(str[i])) && str[i] && str[i] !== '.') {
            endIdx = i - 1;
            break;
        }
    }
    let _val = str.substring(start, endIdx+1);
    if((start > 0 && str[start - 1] === ':') || (endIdx < str.length -1 && str[endIdx + 1] === ':')) {
        let val: IPositionFullRow = {
            type: 'rowPosition',
            value: Number(_val),
            isRelative: true
        }
        return [null, val, endIdx];
    }
    let val: IValueNumber = {
        type: 'value',
        dataType: 'number',
        value: Number(_val)
    }
    return [null, val, endIdx];
}
/**
 * 
 * @param str st
 * @param start 
 */
 const parseCell: ParseFunction = (str: string, start: number, renstu: any) => {
    let endIdx = str.length;
    for(let i = start; i < str.length; i++) {
        if(!/^[A-Z0-9\$]$/.test(str[i])) {
            endIdx = i;
            break;
        }
    }
    let subStr = str.substring(start, endIdx);
    if(str[endIdx] !== '(') {
        if(/^\$*[A-Z]+((\${0}\d*)|(\${1}\d+))$/.test(subStr)) {
            let colInfo = subStr.match(/^\$*[A-Z]*/g)[0];
            let rowInfo = subStr.match(/\$*\d*$/g)[0];
            let rowIsRelative = rowInfo.indexOf('$') === -1;
            let colNum = TableHelper.mapColToNum(colInfo.replace('$', ''));
            if(rowInfo === '') {
                let val: IPositionFullCol = {
                    type: 'colPosition',
                    value: colNum,
                    isRelative: rowIsRelative
                }
                return [null, val, endIdx - 1];
            } else {
                // let col = Number(colInfo.replace('$', ''));
                // console.log(colInfo)
                let val: IPosition = {
                    type: "position",
                    col: colNum,
                    colIsRelative: colInfo.indexOf('$') === -1,
                    row: Number(rowInfo) - 1,
                    rowIsRelative
                }
                return [null, val, endIdx - 1];
            }
        }
    }
    if(KEYWORDS[subStr as keyof typeof KEYWORDS] !== undefined) {
        let val: IKeywords = {
            type: 'keyworks',
            value: subStr
        }
        return [null, val, endIdx - 1];
    }
    let val: IFunction = {
        type: 'function',
        value: subStr
    }
    return [null, val, endIdx - 1];
}
export function parse(str: string) {
    let result: TAllTypes[] = [];
    let err = null;
    for(let i = 0; i < str.length; i++) {
        if(str[i] === ' ') continue;
        if(str[i] === '(') {
            result.push({type:'brackets', value: 'left'});
            continue;
        }
        if(str[i] === ')') {
            result.push({type:'brackets', value: 'right'});
            continue;
        }
        if(str[i] === '{') {
            result.push({type: 'collect', value: '{'});
            continue;
        }
        if(str[i] === '}') {
            result.push({type: 'collect', value: '}'});
            continue;
        }
        if(str[i] ===':') {
            result.push({type: "special",value: ':'});
            continue;
        }
        if(str[i] === '&') {
            result.push({type:'link', value: '&'});
            continue;
        }
        if(str[i] === ',') {
            result.push({
                type: "special",
                value: ','
            });
            continue;
        }
        if(str[i] === '"' || str[i] === `'`) {
            let [err, val, newStartIdx] = parseString(str, i, result);
            if(!err) {
                result.push(val);
                i = newStartIdx;
                continue;
            }
        }
        if(BASE_OPERATE.indexOf(str[i] as OperateSymbol) !== -1) {
            let [err, val, newStartIdx] = paseOperate(str, i, result);
            if(!err) {
                result.push(val);
                i = newStartIdx;
                continue;
            }
        }
        if(/[0-9]/.test(str[i])) {
            let [err, val, newStartIdx] = parseNumber(str, i, result);
            if(!err) {
                result.push(val);
                i = newStartIdx;
                continue;
            }
        }
        if(/[A-Z]/.test(str[i]) ||  str[i] === '$') {
            let [err, val, newStartIdx] = parseCell(str, i, result);
            if(!err) {
                result.push(val);
                i = newStartIdx;
                continue;
            }
        }
        
    }
    for(let i = 0; i < result.length; i++) {
        if(result[i].type === 'special' && (result[i] as ISpecial).value === ':') {
            let pre = result[i - 1];
            let next = result[i + 1];
            if(pre.type === 'value' && pre.dataType === 'number') {
                result[i - 1] = {
                    type: 'rowPosition',
                    isRelative: true,
                    value: pre.value
                }
            }
            if(next.type === 'value' && next.dataType === 'number') {
                result[i+1] = {
                    type: 'rowPosition',
                    isRelative: true,
                    value: next.value
                }
            }
        }
    }
    return result;
}